﻿#region Using's

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Controls;
using LightPersist;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TestDomainModel;
using Debug=System.Diagnostics.Debug;

#endregion

namespace LightPersistTests
{
	[TestClass]
	public class NotifyUI_inverseof_lists : NotifyUI_lists<FakeAnimation>
	{
		[TestInitialize]
		public void NotifyUI_lists_initialize()
		{
			SetListUnderTest(() => child.Animations);
			LogInitializeComplete();
		}

		[TestMethod]
		public void Should_notify_inverseof_lists()
		{
			var animation1 = Context.Create<FakeAnimation>();
			var animation2 = Context.Create<FakeAnimation>();
			Root.Animations.Add(animation1);
			Root.Animations.Add(animation2);

			System.Diagnostics.Debug.WriteLine("!! HASH2 = " + child.Animations.GetHashCode());
			animation1.Controls.Add(child);
			animation2.Controls.Add(child);

			wpfObservable.Add(animation1);
			wpfObservable.Add(animation2);
		}

	}

	[TestClass]
	public class NotifyUI_lists : NotifyUI_lists<FakeControl>
	{
		[TestInitialize]
		public void NotifyUI_lists_initialize()
		{
			Trace.WriteLine(new string('+', 30) + " Test Initializing " + new string('+', 30));
			SetListUnderTest(() => child.Children);
			LogInitializeComplete();
			Trace.WriteLine(new string('+', 30) + " Test Initialized2 " + new string('+', 30));
		}

		[TestMethod]
		public void Should_not_notify_on_subscribe()
		{
			skipNotifyAssert = true;
			Assert.AreEqual(0, fired.Count);
		}

		[TestMethod]
		public void Should_insert_into_middle()
		{
			Check(x => x.Add(newItem));
			Check(x => x.Add(newItem2));
			Check(x => x.Add(newItem3));
			Reconnect();
			Check(x => x.Insert(1, newItem4));
		}

		[TestMethod]
		public void Should_remove()
		{
			Check(x => x.Add(newItem));
			Check(x => x.Add(newItem2));
			Check(x => x.Add(newItem3));
			Reconnect();
			Check(x => x.RemoveAt(1));
		}

		[TestMethod]
		public void Should_replace()
		{
			Check(x => x.Add(newItem));
			Check(x => x.Add(newItem2));
			Check(x => x.Add(newItem3));
			Reconnect();
			Check(x => x[1] = newItem4);
		}

		[TestMethod]
		public void Should_remove_2_item()
		{
			Check(x => x.Add(newItem));
			Check(x => x.Add(newItem2));
			Check(x => x.Add(newItem3));
			Reconnect();
			Check(x => x.RemoveAt(0));
			Check(x => x.RemoveAt(0));

			foreach(var args in fired)
			{
				Trace.WriteLine(string.Format("{0}: iOld={1}, iNew={2}, listOld={3}, listNew={4}", args.Action,
				                              args.OldStartingIndex, args.NewStartingIndex, args.OldItems, args.NewItems));
			}
		}

		[TestMethod]
		public void Should_inform_on_clear()
		{
			Check(x => x.Add(newItem));
			Reconnect();
			Check(x => x.Clear());
		}

		[TestMethod]
		public void Should_perform_complex_colelction_changes()
		{
			var rnd = new Random();
			const int ItemCount = 10;
			for(var i = 0; i < ItemCount; i++)
			{
				var newItemToAdd = Context.Create<FakeControl>();
				Check(x => x.Add(newItemToAdd));
			}
			Reconnect();
			for(var i = 0; i < 10; i++)
			{
				var id = rnd.Next(wpfObservable.Count);
				switch(rnd.Next(3))
				{
					case 0: // remove
						Check(x => x.RemoveAt(id));
						break;
					case 1: // move
						var idTo = rnd.Next(wpfObservable.Count - 1);
						Check(x =>
						{
							var item = x[id];
							x.RemoveAt(id);
							x.Insert(idTo, item);
						});
						break;
					case 2: // insert new
						var newItemToAdd = Context.Create<FakeControl>();
						Check(x => x.Add(newItemToAdd));
						break;
					default:
						throw null;
				}

				Reconnect();
				Assert_notify_result_are_same_as_in_wpf();
			}
		}

		[TestMethod]
		public void Should_bulk_insert()
		{
			Assert.Inconclusive();
		}

		[TestMethod]
		public void Should_ensure_that_this_test_framework_are_working()
		{
			skipNotifyAssert = true;
			wpfObservable.Add(Context.Create<FakeControl>());
			Assert.AreEqual(1, listViewWpf.Items.Count);
		}

		[TestMethod]
		public void Should_add()
		{
			Check(x => x.Add(newItem));

			Trace.WriteLine("%%%%%%%%%%%");
			foreach(var args in firedPropertyByWpf)
			{
				Trace.WriteLine(args.PropertyName);
			}
		}

		[TestMethod]
		public void Should_add_for_Undo()
		{
			Check(x => x.Add(newItem));

			Check();

			Context.Undo();
			wpfObservable.Remove(newItem);

			tr = Context.BeginTransaction();
			Check();

			Context.Redo();
			wpfObservable.Add(newItem);
			tr = Context.BeginTransaction();
		}

		[TestMethod]
		public void Should_insert_into_end()
		{
			Check(x => x.Add(newItem));
			Check(x => x.Insert(1, newItem2));
		}

		[TestMethod]
		public void Should_bulk_remove()
		{
			Assert.Inconclusive();
		}

		[TestMethod]
		public void Should_replace_all()
		{
			Assert.Inconclusive();
		}

		[TestMethod]
		public void Should_optimize_changes()
		{
			Assert.Inconclusive();
		}

		[TestMethod]
		public void Should_provide_interface()
		{
			skipNotifyAssert = true;
			Assert.IsTrue(child.Children is INotifyCollectionChanged);
		}
	}

	public class NotifyUI_lists<T> : ContextTestsBase where T : ModelBase
	{
		protected readonly List<NotifyCollectionChangedEventArgs> fired = new List<NotifyCollectionChangedEventArgs>();
		protected readonly List<NotifyCollectionChangedEventArgs> firedByWpf = new List<NotifyCollectionChangedEventArgs>();
		protected readonly List<PropertyChangedEventArgs> firedProperty = new List<PropertyChangedEventArgs>();
		protected readonly List<PropertyChangedEventArgs> firedPropertyByWpf = new List<PropertyChangedEventArgs>();
		protected readonly ListView listViewLightList = new ListView();
		protected readonly ListView listViewWpf = new ListView();

		protected readonly ObservableCollection<T> wpfObservable = new ObservableCollection<T>();
		protected Func<IEnumerable> ListUnderTest;
		protected T newItem;
		protected T newItem2;
		protected T newItem3;
		protected T newItem4;
		protected bool skipNotifyAssert;

		protected void SetListUnderTest(Func<IEnumerable> lut)
		{
			if(ListUnderTest != null)
			{
				((INotifyCollectionChanged)ListUnderTest()).CollectionChanged -= Children_CollectionChanged;
			}

			ListUnderTest = lut;
			listViewLightList.ItemsSource = ListUnderTest();
			var obj = ListUnderTest();
			System.Diagnostics.Debug.WriteLine("!! HASH = " + obj.GetType().Name + " " + obj.GetHashCode());
			System.Diagnostics.Debug.WriteLine("!! HASH = " + obj.GetType().Name + " " + obj.GetHashCode());
			((INotifyCollectionChanged)ListUnderTest()).CollectionChanged += Children_CollectionChanged;
		}

		protected void Children_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			traceEvent(e, "[LP]");
			fired.Add(e);
			Assert.AreEqual(ListUnderTest(), sender);
		}

		[TestInitialize]
		public void Check_notify_property_init()
		{
			newItem = Context.Create<T>();
			newItem2 = Context.Create<T>();
			newItem3 = Context.Create<T>();
			newItem4 = Context.Create<T>();

			ResetFiredEventsLog();

			listViewWpf.ItemsSource = wpfObservable;

			wpfObservable.CollectionChanged += (s, e) =>
			{
				traceEvent(e, "[WPF]");
				firedByWpf.Add(e);
				Assert.AreEqual(wpfObservable, s);
			};
			((INotifyPropertyChanged)wpfObservable).PropertyChanged += (s, e) =>
			{
				firedPropertyByWpf.Add(e);
				Assert.AreEqual(wpfObservable, s);
			};

			Trace.WriteLine(new string('=',20)+" Check_notify_property_init");
			LogInitializeComplete();
		}

		protected static void traceEvent(NotifyCollectionChangedEventArgs e, string s)
		{
			Trace.WriteLine(s
			                +
			                ": {0} old_id={1} new_id={2} old_item={3} new_item={4}".Arg(e.Action, e.NewStartingIndex,
			                                                                            e.OldStartingIndex,
			                                                                            firstOrDef(e.OldItems),
			                                                                            firstOrDef(e.NewItems)));
		}

		protected static object firstOrDef(IList list)
		{
			if(list == null)
			{
				return null;
			}
			return list.Count > 0 ? list[0] : null;
		}

		protected void Check()
		{
			NotifyUI_lists_Cleanup();
		}

		[TestCleanup]
		public void NotifyUI_lists_Cleanup()
		{
			if(!skipNotifyAssert)
			{
				System.Diagnostics.Debug.WriteLine(child.GetHashCode());
				tr.CommitTransaction();
				Assert_notify_result_are_same_as_in_wpf();
			}
		}

		protected void ResetFiredEventsLog()
		{
			ChangeTransaction();
			fired.Clear();
		}

		protected void Check(Action<IList<T>> act)
		{
			Check<T>(act);
		}

		protected void Check<V>(Action<IList<V>> act)
		{
			var before = fired.Count;
			act((IList<V>)ListUnderTest());
			act((IList<V>)wpfObservable);
			Assert.AreEqual(before, fired.Count, "Should not raise events inside transaction");
		}

		protected string collectionToString(IEnumerable col)
		{
			return string.Join(", ", col.OfType<object>().Select(x => x.ToString()).ToArray());
		}

		protected void Assert_notify_result_are_same_as_in_wpf()
		{
			Trace.WriteLine("ListView LightList " + listViewLightList.Items.Count + " items");
			Trace.WriteLine("ListView Wpf " + listViewWpf.Items.Count + " items");

			// this cast are required. Resharper4.1 has a bug about redundant cast
			Equal((IEnumerable)listViewWpf.Items.Cast<object>(), listViewLightList.Items.Cast<object>());

			// throw collected asserts!
			try
			{
				Teardown();
			}
			catch
			{
				Trace.WriteLine("Expect: " + collectionToString(listViewWpf.Items));
				Trace.WriteLine("Actual: " + collectionToString(listViewLightList.Items));
				throw;
			}

			// TODO replace stupid comparison of logged events to real binding of ListView (in separate thread?) and comparing of items
			//			try
			//			{
			//				Assert.AreEqual(fired.Count, firedByWpf.Count, "Raised events count");
			//				for(var i = 0; i < fired.Count; i++)
			//				{
			//					Assert.AreEqual(fired[i].Action, firedByWpf[i].Action, "Action");
			//					Assert.AreEqual(fired[i].NewStartingIndex, firedByWpf[i].NewStartingIndex, "NewStartingIndex");
			//					Assert.AreEqual(fired[i].OldStartingIndex, firedByWpf[i].OldStartingIndex, "OldStartingIndex");
			//					CollectionAssert.AreEqual(fired[i].NewItems, firedByWpf[i].NewItems, "NewItems");
			//					CollectionAssert.AreEqual(fired[i].OldItems, firedByWpf[i].OldItems, "OldItems");
			//				}
			//			}
			//			catch
			//			{
			//				Trace.WriteLine("=== Oh, poor man... ok, thats how WPF notify in such situation: ===");
			//				foreach(var args in firedByWpf)
			//				{
			//					Trace.WriteLine(string.Format("{0}: iOld={1}, iNew={2}, listOld={3}, listNew={4}", args.Action,
			//																																			args.OldStartingIndex, args.NewStartingIndex, args.OldItems, args.NewItems));
			//				}
			//				throw;
			//			}
		}

		new protected void Reconnect()
		{
			ChangeTransaction();
			child = Context.GetObjectByID<FakeControl>(child.ID);
		}


	}
}